package pay

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"

	"gitee.com/githubdog/goci/libraries/mylog"
	"gitee.com/githubdog/goci/tools/helper"
	"gitee.com/githubdog/goci/tools/myerr"
)

type appidKey struct {
	Public  string // 支付宝公钥
	Private string // 项目方私钥
}

// AliPayMap 支付宝支付map类型
type AliPayMap map[string]string

// AlipayCommonResponse 支付宝公共返回
type AlipayCommonResponse struct {
	Code    string `json:"code,omitempty"`
	Msg     string `json:"msg,omitempty"`
	SubCode string `json:"sub_code,omitempty"`
	SubMsg  string `json:"sub_msg,omitempty"`
}

// AliPayRefundResponse 支付宝退款返回
type AliPayRefundResponse struct {
	AlipayCommonResponse
	TradeNo              string `json:"trade_no,omitempty"`
	OutTradeNo           string `json:"out_trade_no,omitempty"`
	BuyerLogonID         string `json:"buyer_logon_id,omitempty"`
	FundChange           string `json:"fund_change,omitempty"`
	RefundFee            string `json:"refund_fee,omitempty"`
	RefundCurrency       string `json:"refund_currency,omitempty"`
	RefundDetailItemList struct {
		FundChannel string `json:"fund_channel,omitempty"`
		Amount      string `json:"amount,omitempty"`
		RealAmount  string `json:"real_amount,omitempty"`
		FundType    string `json:"fund_type,omitempty"`
	} `json:"refund_detail_item_list,omitempty"`
	StoreName                    string `json:"store_name,omitempty"`
	BuyerUserID                  string `json:"buyer_user_id,omitempty"`
	SendBackFee                  string `json:"send_back_fee,omitempty"`
	RefundSettlementID           string `json:"refund_settlement_id,omitempty"`
	PresentRefundBuyerAmount     string `json:"present_refund_buyer_amount,omitempty"`
	PresentRefundDiscountAmount  string `json:"present_refund_discount_amount,omitempty"`
	PresentRefundMdiscountAmount string `json:"present_refund_mdiscount_amount,omitempty"`
	HasDepositBack               string `json:"has_deposit_back,omitempty"`
}

type AliPayPagePayResponse struct {
	AlipayCommonResponse
	TradeNo         string `json:"trade_no,omitempty"`
	OutTradeNo      string `json:"out_trade_no,omitempty"`
	SellerID        string `json:"seller_id,omitempty"`
	TotalAmount     string `json:"total_amount,omitempty"`
	MerchantOrderNo string `json:"merchant_order_no,omitempty"`
}

// AliPayQueryResponse 支付宝订单查询返回
type AliPayQueryResponse struct {
	AlipayCommonResponse
	BuyerLogonID   string `json:"buyer_logon_id"`
	BuyerPayAmount string `json:"buyer_pay_amount"`
	BuyerUserID    string `json:"buyer_user_id"`
	InvoiceAmount  string `json:"invoice_amount"`
	OutTradeNo     string `json:"out_trade_no"`
	PointAmount    string `json:"point_amount"`
	ReceiptAmount  string `json:"receipt_amount"`
	SendPayDate    string `json:"send_pay_date"`
	Totalamount    string `json:"total_amount"`
	TradeNo        string `json:"trade_no"`
	TradeStatus    string `json:"trade_status"`
	// TODO ...
}

// 文档: https://opendocs.alipay.com/apis/api_1/alipay.trade.app.pay?scene=API002020081300013627

var (
	gatewayURL = "https://openapi.alipay.com/gateway.do"
	// 订单查询返回数据匹配
	oqSignContentReg = regexp.MustCompile(`^\s?\{[^\{]+(\{.+\})[^\}]*\}\s?$`)
	oqSignReg        = regexp.MustCompile(`^\s?\{.*\"sign\":\"([^\"]+)\".*\}\s?$`)
	// appid对应的公私钥
	TestAppID string = "000001"
	keyMap           = map[string]appidKey{
		TestAppID: { // 测试
			Public:  "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAulgyIZz8UFu6DhEWG5Eby272znM4DnwDZNozylKJGmZrlTmSLI/2wPuXd2FwiQzkuseMhL+DpncxiZZd32GxjSjRFEMLYoKJcN77OSxHyssd+HzVmHGaMfPpITbW+aInm4IjGtUsd3FW3yLnzYBE62r2pOWr5qUyNdSUhcAM8qxLgS1yzGKdWExtYYXlRYLTOIXdo0ZijTZQlqrOK/JJbl+e1irIfeGKWkNO14vBz767MvWoNEoavKP+gIneWhdS8hnnYnoXkJjCeCGnSkA6gQGlRrF1Wx18iUsnVrcqTlufJ7d9mJGIgrA/Fe/peawe7Plf7X9uiTQhn59kSucvuwIDAQAB",
			Private: "MIIEpAIBAAKCAQEAulgyIZz8UFu6DhEWG5Eby272znM4DnwDZNozylKJGmZrlTmSLI/2wPuXd2FwiQzkuseMhL+DpncxiZZd32GxjSjRFEMLYoKJcN77OSxHyssd+HzVmHGaMfPpITbW+aInm4IjGtUsd3FW3yLnzYBE62r2pOWr5qUyNdSUhcAM8qxLgS1yzGKdWExtYYXlRYLTOIXdo0ZijTZQlqrOK/JJbl+e1irIfeGKWkNO14vBz767MvWoNEoavKP+gIneWhdS8hnnYnoXkJjCeCGnSkA6gQGlRrF1Wx18iUsnVrcqTlufJ7d9mJGIgrA/Fe/peawe7Plf7X9uiTQhn59kSucvuwIDAQABAoIBAA1vUGX2AIb2pgpflv1zz9Ix9N9blT4OGNyLktJScwoROk5u8Sf/VSYCvZ/gyZFH+bGxs2Lav+4fh9XInon9bCSmPbS9RLPcf3ZAzVII7EAltsp0jZ3JCvnxAOzhHEyxMTPYBRnpJqSR6lZ6GMZLKizi8FdhK6ycWmthITgnaYeRXvYjAp3yaFmOIc5JD8Z7j8ryGcfr9Qwni6EbS/lssVzdqIFtf1nuXXgwqxo/sUFpLMc92HOpCCipDLiA2O+OUeSR93wBRY69pGZYJuhL4DI81/Xsa8GPqLYtXVBsizD3hXfn0HvN8Ib8M3xu0wt8cYUFlhYEF4kjtOwXATtvovECgYEA5BanVCrBljLHaLhC8/LHTEuLky5ZY/mPuEECbkeLEPvu7i35FWkvZDgSXZvkCq2DlwNXbUT0eZcrTLBGj13yxADV3TJ4QEOhCkaIp8RrfiG8C7FtZIRh1dJCutGy9h5VPzWua5cHlD3MZwIJmB6fLrV8Iss0HqQ5r3Fa5oi5yjMCgYEA0SXTSQCa47cFS/B2B/DdiX+z3y3o/J7HmjIQxlyJvp7P+O2nEK4z8TlRivIk5dTI3Z8IRuGZzC6P+KafOl/tplqnPX9W+zijrcq7/BXMeUqSr+r1MnxIxmtJCK1xPBH4eyzASSCrXX2Tr7vJTkAmnordyBKgZhh9gPudAPVUjFkCgYABeN/1nB0JwiQlWeMcvflpVad9+LOdAJRb5ReF7LJKFsYxBVzxNW+X3wDHV+U96GbMB7p9uw805bPi19Rswnd5qmO2/Mdn6sfUUCY7pvnmo4MaNDf3iR80iRnjKM1V51Y3hTot+ZiT7V4vYAT91VzDqWo2s0cP15GDv2CN0l4YnwKBgQCx9mKbSqWBQBmErX5xcVXAiuYt8G3Ajwa+wP0fHFyk05OZd/bhLanLB3/IIBSfb/jfA1RSo+JvJ+ufo3/3durL03Vc0b7ckyxLVCey74WGa5r3j5DGPZTbKS0C8jpY592sSb7wOpk2OwVw0fQP3mw+GWFvvdG4LfyUxQSqbRZdOQKBgQCVrS2+bZzkuMbVi+kkSp3M/9INHwnEOkVppQEekX145usldgcF0Kj2V0YpZpQ3SdYVW7ZmuJ1jg8gDWt3gsu3h5A8nR3u1NftpmcxhoZuR/rivZ45kOuDSerPHr7yG3k0GyCImGqq88hYpIXbZJwVys16hlcmLCaNx43hYBJY91w==",
		},
	}
	LogAliChannel string = "alipay"
	LogAliFile    string
)

// pubkey 阿里公钥
// prikey 我方私钥, 对应公钥给阿里
func AppendKeyMap(appid, pubkey, prikey string) {
	keyMap[appid] = appidKey{
		Public:  pubkey,
		Private: prikey,
	}
}

func GetPrivateByAppID(appid string) string {
	return keyMap[appid].Private
}

// InitAliPay 初始化支付宝支付对象
func InitAliPay(appid string) AliPayMap {
	// 缺少: [非必须]return_url, [非必须]notify_url, [必须]sign, [必须]biz_content
	return AliPayMap{
		"app_id":    appid,
		"format":    "JSON",
		"charset":   "utf-8",
		"sign_type": "RSA2",
		"version":   "1.0",
		"timestamp": time.Now().Format("2006-01-02 15:04:05"),
	}
}

func (ali AliPayMap) SetAliPay(key, value string) AliPayMap {
	ali[key] = value
	return ali
}

// SetBizContent 设置请求内容
func (ali AliPayMap) SetBizContent(req map[string]string) AliPayMap {
	// json序列化比较耗性能
	arr := make([]string, 0, len(req))
	for k, v := range req {
		if strings.TrimSpace(v) != "" {
			arr = append(arr, "\""+k+"\":\""+v+"\"")
		}
	}
	if len(arr) > 0 {
		ali["biz_content"] = "{" + strings.Join(arr, ",") + "}"
	}
	return ali
}

// SignAndEncodeParam 签名并生成转义字符串
func (ali AliPayMap) SignAndEncodeParam() (ret string, err error) {
	if v, ok := keyMap[ali["app_id"]]; !ok {
		err = errors.New("appid无效")
	} else if sign, e := HandleSign(v.Private, ali); e != nil {
		err = e
	} else {
		ali["sign"] = sign
		ret = EncodeParam(ali)
	}
	return
}

func HandleSign(privite string, data map[string]string) (string, error) {
	var (
		err      error
		sign     string
		keyBytes []byte
	)
	if keyBytes, err = helper.FormatKey(privite, 1); err == nil {
		signContent := helper.GetSignContent(data, "")
		sign, err = helper.RsaSignWithSha256([]byte(signContent), keyBytes)
	}
	return sign, err
}

// VerifySign 验签
func VerifySign(appid, sign string, dataBytes []byte) (ret bool, err error) {
	if v, ok := keyMap[appid]; !ok {
		err = errors.New("appid无效")
	} else if keyBytes, e := helper.FormatKey(v.Public, 0); e == nil {
		ret, err = helper.VerifySignWithSha256(sign, dataBytes, keyBytes)
	} else {
		err = e
	}
	return
}

// VerifySignByAliCbRaw 通过post的raw数据进行验签
/*
gmt_create=2022-09-15+09%3A34%3A17&charset=utf-8&seller_email=linzy120%40126.com&subject=%E8%8B%B9%E6%9E%9C%E4%BA%A7%E5%93%81%E4%B8%93%E5%8C%BA&sign=JD4%2BUQG57tIcYwOyQ2So8chSZ2bmrDIgSkXgw%2B%2BeWgXc5%2FAfe3vLKbqDfG9kCGzAm0holW9FTkc24GEyVIYGzZk%2Bhp7SJVCFfd8J2TPmhrUB%2BGwFvZamZAttD2W2zWMJbGERSQNTIGsoMeEB1E%2BSmZoe8WbBDYZdjiFlXCyAJ%2FS1gY4K2dMuri6yucveJjnr8ASSNnXZtnpu4I2Rz2NThrQHEuGvKbQGR%2BcYbf5a7PdPOE27PS1j99FhSUonPz2lAJ7Q4uJ%2Fw%2B8O1T44nH1vLRqJNu74qnI1VESVySwd1Uu57iI7UBn9ioSaxxg4ORyzjNjchZ31ad2EGENmkd9Ptw%3D%3D&buyer_id=2088002618507872&invoice_amount=2.00&notify_id=2022091500222093418007871443815024&fund_bill_list=%5B%7B%22amount%22%3A%222.00%22%2C%22fundChannel%22%3A%22ALIPAYACCOUNT%22%7D%5D&notify_type=trade_status_sync&trade_status=TRADE_SUCCESS&receipt_amount=2.00&app_id=2021003142672804&buyer_pay_amount=2.00&sign_type=RSA2&seller_id=2088441128571072&gmt_payment=2022-09-15+09%3A34%3A17&notify_time=2022-09-15+09%3A34%3A18&version=1.0&out_trade_no=e7b3c9459bae4276acace35558a3e08c&total_amount=2.00&trade_no=2022091522001407871420636205&auth_app_id=2021003142672804&buyer_logon_id=wo9***%40126.com&point_amount=0.00

字段说明: https://opendocs.alipay.com/open/204/105301?pathHash=fef00e6d&ref=api

gmt_create=2022-09-15+09%3A34%3A17
&charset=utf-8
&seller_email=linzy120%40126.com
&subject=%E8%8B%B9%E6%9E%9C%E4%BA%A7%E5%93%81%E4%B8%93%E5%8C%BA
&sign=JD4%2BUQG57tIcYwOyQ2So8chSZ2bmrDIgSkXgw%2B%2BeWgXc5%2FAfe3vLKbqDfG9kCGzAm0holW9FTkc24GEyVIYGzZk%2Bhp7SJVCFfd8J2TPmhrUB%2BGwFvZamZAttD2W2zWMJbGERSQNTIGsoMeEB1E%2BSmZoe8WbBDYZdjiFlXCyAJ%2FS1gY4K2dMuri6yucveJjnr8ASSNnXZtnpu4I2Rz2NThrQHEuGvKbQGR%2BcYbf5a7PdPOE27PS1j99FhSUonPz2lAJ7Q4uJ%2Fw%2B8O1T44nH1vLRqJNu74qnI1VESVySwd1Uu57iI7UBn9ioSaxxg4ORyzjNjchZ31ad2EGENmkd9Ptw%3D%3D
&buyer_id=2088002618507872
&invoice_amount=2.00
&notify_id=2022091500222093418007871443815024
&fund_bill_list=%5B%7B%22amount%22%3A%222.00%22%2C%22fundChannel%22%3A%22ALIPAYACCOUNT%22%7D%5D
&notify_type=trade_status_sync
&trade_status=TRADE_SUCCESS
&receipt_amount=2.00
&app_id=2021003142672804
&buyer_pay_amount=2.00
&sign_type=RSA2
&seller_id=2088441128571072
&gmt_payment=2022-09-15+09%3A34%3A17
&notify_time=2022-09-15+09%3A34%3A18
&version=1.0
&out_trade_no=e7b3c9459bae4276acace35558a3e08c
&total_amount=2.00
&trade_no=2022091522001407871420636205
&auth_app_id=2021003142672804
&buyer_logon_id=wo9***%40126.com
&point_amount=0.00
*/
func VerifySignByAliCbRaw(raw string, debug bool) (map[string]string, myerr.ErrMsg) {
	postMap := map[string]string{}
	ret := myerr.ErrMsg{}
	post := helper.ParsePostRawData(raw, nil)
	if data, ok := post.(map[string]string); ok {
		postMap = data
		if s, ok := data["sign"]; ok {
			signContent := helper.GetSignContent(data, "sign,sign_type")
			if r, err := VerifySign(data["app_id"], s, []byte(signContent)); err != nil {
				ret.Err = err
			} else if r {
				ret.Ret = true
			} else {
				ret.Msg = "验签失败"
			}
		} else {
			ret.Msg = "POST数据不含[sign]参数"
		}
	} else {
		ret.Msg = "POST内容为空或解析出错"
	}

	if debug {
		logParams := []interface{}{
			fmt.Sprintf("VerifySignByAliCbRaw 回调内容:[%s]", raw),
		}
		if ret.Ret {
			logParams = append(logParams, "\n验签成功!")
		} else if ret.Err != nil {
			logParams = append(logParams, "\n出错:", ret.Err)
		} else {
			logParams = append(logParams, "\n验签失败:", ret.Msg)
		}
		mylog.WriteLog(LogAliChannel, LogAliFile, logParams...)
	}

	return postMap, ret
}

// EncodeParam 转义参数
func EncodeParam(data map[string]string) string {
	val := url.Values{}
	for k, v := range data {
		val.Add(k, v)
	}
	return val.Encode()
}

func (ali AliPayMap) requestAlipay() ([]byte, myerr.ErrMsg) {
	var (
		ret    = myerr.ErrMsg{}
		dBytes []byte
	)
	if str, err := ali.SignAndEncodeParam(); err != nil {
		ret.Err = err
	} else if request, err := http.NewRequest(http.MethodGet, gatewayURL+"?"+str, nil); err != nil {
		ret.Err = err
	} else if resp, err := http.DefaultClient.Do(request); err != nil {
		ret.Err = err
	} else {
		defer resp.Body.Close()
		if body, err := ioutil.ReadAll(resp.Body); err != nil {
			ret.Err = err
		} else if find1 := oqSignContentReg.FindSubmatch(body); len(find1) < 2 {
			ret.Msg = "返回内容未匹配到有效数据"
		} else if find2 := oqSignReg.FindSubmatch(body); len(find2) < 2 {
			ret.Msg = "返回内容未匹配到签名数据"
		} else if r, e := VerifySign(ali["app_id"], string(find2[1]), find1[1]); e != nil {
			ret.Err = e
		} else if !r {
			ret.Msg = "verify sign fail!"
		} else {
			dBytes = find1[1]
			ret.Msg = "success"
			ret.Ret = true
		}
	}
	return dBytes, ret
}

// QueryOrderInfo 查讯订单信息
func QueryOrderInfo(appid, outTradeNo, tradeNo string) (AliPayQueryResponse, myerr.ErrMsg) {
	var (
		payObj = InitAliPay(appid)
		ret    = myerr.ErrMsg{}
		data   = AliPayQueryResponse{}
	)

	payObj["method"] = "alipay.trade.query"
	bizContent := map[string]string{}
	if tradeNo == "" && outTradeNo == "" {
		ret.Msg = "查询参数为空"
	} else {
		if tradeNo != "" {
			bizContent["trade_no"] = tradeNo
		}
		if outTradeNo != "" {
			bizContent["out_trade_no"] = outTradeNo
		}

		payObj.SetBizContent(bizContent)
		// {
		// 	"code":"10000",
		// 	"msg":"Success",
		// 	"buyer_logon_id":"wo9***@126.com",
		// 	"buyer_pay_amount":"0.00",
		// 	"buyer_user_id":"2088002618507872",
		// 	"invoice_amount":"0.00",
		// 	"out_trade_no":"20210519091048647713",
		// 	"point_amount":"0.00",
		// 	"receipt_amount":"0.00",
		// 	"send_pay_date":"2021-05-19 09:11:13",
		// 	"total_amount":"0.03",
		// 	"trade_no":"2021051922001407871413276388",
		// 	"trade_status":"TRADE_SUCCESS"
		// }
		var _bytes []byte
		if _bytes, ret = payObj.requestAlipay(); ret.Ret {
			ret.Ret = false
			if e := json.Unmarshal(_bytes, &data); e != nil {
				ret.Msg = "json.Unmarshal:" + e.Error()
			} else if data.Code == "10000" {
				ret.Ret = true
				ret.Msg = "success"
			} else if data.SubMsg != "" {
				ret.Msg = fmt.Sprintf("[%s]%s", data.SubCode, data.SubMsg)
			} else if data.Msg != "" {
				ret.Msg = fmt.Sprintf("[%s]%s", data.Code, data.Msg)
			} else {
				ret.Msg = "查询返回失败"
			}
		}
		if !ret.Ret {
			mylog.WriteLog(LogAliChannel, LogAliFile, "查询失败", ret.Err, ret.Msg, "\n", string(_bytes))
		}
	}

	return data, ret
}

// AliTradeRefund 阿里交易退款
func AliTradeRefund(appid, outTradeNo, outRequestNo string, money float64) (AliPayRefundResponse, myerr.ErrMsg) {
	var (
		payObj = InitAliPay(appid)
		data   = AliPayRefundResponse{}
		ret    = myerr.ErrMsg{}
	)

	payObj["method"] = "alipay.trade.refund"
	bizContent := map[string]string{}
	if outTradeNo == "" {
		ret.Msg = "缺少订单号参数"
	} else if money < 0.01 {
		ret.Msg = "退款金额不合法"
	} else {
		bizContent["refund_amount"] = strconv.FormatFloat(money, 'f', 2, 32)
		bizContent["out_trade_no"] = outTradeNo
		if outRequestNo != "" {
			bizContent["out_request_no"] = outRequestNo
		}

		var _bytes []byte
		if _bytes, ret = payObj.SetBizContent(bizContent).requestAlipay(); ret.Ret {
			ret.Ret = false
			if e := json.Unmarshal(_bytes, &data); e != nil {
				ret.Msg = "json.Unmarshal:" + e.Error()
			} else if data.Code == "10000" {
				ret.Ret = true
				ret.Msg = "success"
			} else if data.SubMsg != "" {
				ret.Msg = fmt.Sprintf("[%s]%s", data.SubCode, data.SubMsg)
			} else if data.Msg != "" {
				ret.Msg = fmt.Sprintf("[%s]%s", data.Code, data.Msg)
			} else {
				ret.Msg = "退款返回失败"
			}
		}
		if !ret.Ret {
			mylog.WriteLog(LogAliChannel, LogAliFile, "退款失败", ret.Err, ret.Msg, "\n", string(_bytes))
		}
	}

	return data, ret
}

func (ali AliPayMap) AliTradePagePay(money float64, orderContent map[string]string) (string, myerr.ErrMsg) {
	var (
		payUrl string
		err    error
		ret    = myerr.ErrMsg{}
	)

	ali.SetAliPay("method", "alipay.trade.page.pay")
	bizContent := orderContent
	if money < 0.01 || money > 100000000 {
		ret.Msg = "收款金额不合法"
	} else {
		bizContent["total_amount"] = helper.FloatToString(money)
		bizContent["product_code"] = "FAST_INSTANT_TRADE_PAY"

		if payUrl, err = ali.SetBizContent(bizContent).SignAndEncodeParam(); err != nil {
			ret.Err = err
		} else {
			ret.Ret = true
			ret.Msg = "success"
			payUrl = gatewayURL + "?" + payUrl
		}
	}

	return payUrl, ret
}
